Dieses Notebook bereitet die Daten für die Intelligent Zoning Engine vor. Es speichert

Bereits in anderen scripten wurde vorbereitet:

Die Daten werden wiefolgt vorbereitet:

TODO: - die sozioökonomischen Faktoren werden aus den Wahlbezirken auf die Blöcke hochgerechnet (https://github.com/berlinermorgenpost/cogran)

Laden der Daten

sampled_buildings = read_rds('output/sampled_buildings.rds')
bez = readOGR('download/RBS_OD_BEZ_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_BEZ_2015_12.geojson", layer: "OGRGeoJSON"
with 13 features
It has 2 fields
blk = readOGR('download/RBS_OD_BLK_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_BLK_2015_12.geojson", layer: "OGRGeoJSON"
with 15720 features
It has 4 fields
lor = readOGR('download/RBS_OD_LOR_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_LOR_2015_12.geojson", layer: "OGRGeoJSON"
with 447 features
It has 8 fields
re_schulstand = readOGR('download/re_schulstand.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/re_schulstand.geojson", layer: "OGRGeoJSON"
with 709 features
It has 20 fields

Schulwege

route_matrix = read_rds('output/route_matrix.rds')

Schulkapazitäten und Einwohnerzahler auf LOR-Ebene

kapas = read_csv('download/anmeldezahlen.csv') %>% filter(grepl('G', Schulnummer)) %>% filter(!is.na(`Plätze`))
Parsed with column specification:
cols(
  Bezirk = col_character(),
  Schulnummer = col_character(),
  Schulname = col_character(),
  Plätze = col_integer(),
  Anmeldungen = col_character()
)
einwohner_lor = read_delim('download/EWR201512E_Matrix.csv', delim=';')
Parsed with column specification:
cols(
  .default = col_character(),
  ZEIT = col_integer(),
  STADTRAUM = col_integer(),
  E_E = col_number(),
  E_EM = col_number(),
  E_EW = col_number(),
  E_E50_55 = col_number(),
  E_E25U55 = col_number(),
  E_E55U65 = col_number(),
  E_E65U80 = col_number()
)
See spec(...) for full column specifications.

Überprüfung der Vollständigkeit der Daten über Anmeldezahlen/Kapazitäten

re_schulstand_df = re_schulstand %>% as.data.frame() %>% rename(lon=coords.x1, lat=coords.x2)
re_schulstand_df %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>%
  group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
  rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n()))
Joining, by = "Bezirk"

Für welche Bezirke haben wir für alle Schulen Kapazitäten gegeben?

re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>% group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
  rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n())) %>% filter(`Anzahl Schulen` == `Mit Kapazität`)
Joining, by = "Bezirk"

Überprüfung ob die Liste der Schulen und Liste der Schulen mit Kapazitätsinformationen gleich sind:

bezirk = 'Tempelhof-Schöneberg'
schulen_mit_kapa = kapas %>% filter(Bezirk == bezirk) %>% .$Schulnummer
schulen_mit_kapa
 [1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12"
 [9] "07G13" "07G14" "07G15" "07G16" "07G17" "07G18" "07G19" "07G20"
[17] "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28"
[25] "07G29" "07G30" "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
07G01

07G02

07G03

07G05

07G06

07G07

07G10

07G12

07G13

07G14

07G15

07G16

07G17

07G18

07G19

07G20

07G21

07G22

07G23

07G24

07G25

07G26

07G27

07G28

07G29

07G30

07G31

07G32

07G34

07G35

07G36

07G37
grundschulen = re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK == bezirk) %>% .$spatial_name
'In Anmeldeliste, fehlt in Schulstand'
[1] "In Anmeldeliste, fehlt in Schulstand"
In Anmeldeliste, fehlt in Schulstand
setdiff(schulen_mit_kapa, grundschulen)
character(0)
'In re_schulstand, fehlt in Anmeldeliste'
[1] "In re_schulstand, fehlt in Anmeldeliste"
In re_schulstand, fehlt in Anmeldeliste
setdiff(grundschulen, schulen_mit_kapa)
character(0)
map = get_map('Berlin')
Map from URL : http://maps.googleapis.com/maps/api/staticmap?center=Berlin&zoom=10&size=640x640&scale=2&maptype=terrain&language=en-EN&sensor=false
Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address=Berlin&sensor=false
re_schulstand_df_w_kapas = re_schulstand_df %>% left_join(kapas, by=c('spatial_name'='Schulnummer'))

Plot aller Schulen, mit der Info, ob Kapazitätsinformationen verfügbar sind.

data = re_schulstand_df_w_kapas %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk) %>% mutate(missing.capa=is.na(`Plätze`))
ggmap(map) + geom_point(aes(lon, lat, color=missing.capa), data=data) +
    coord_map(xlim=c(min(data$lon)-0.01, max(data$lon)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Filter auf Schulen mit Kapazitätsinformationen (für T-S sind das alle):

relevant_schools = re_schulstand_df_w_kapas %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk & !is.na(`Plätze`)) %>% .$spatial_name
relevant_schools
 [1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12"
 [9] "07G13" "07G14" "07G15" "07G16" "07G17" "07G18" "07G19" "07G20"
[17] "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28"
[25] "07G29" "07G30" "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
07G01

07G02

07G03

07G05

07G06

07G07

07G10

07G12

07G13

07G14

07G15

07G16

07G17

07G18

07G19

07G20

07G21

07G22

07G23

07G24

07G25

07G26

07G27

07G28

07G29

07G30

07G31

07G32

07G34

07G35

07G36

07G37

Mapping Bezirk->LOR->Block

df_bez = as.data.frame(bez)
df_lor = as.data.frame(lor)
df_blk = as.data.frame(blk)

Sanity-Check: LORs und Blöcke im Bezirk

bez_id = filter(df_bez, BEZNAME == bezirk)$BEZ
relevant_lors = df_lor %>% filter(BEZ == bez_id)
relevant_blks = df_blk %>% filter(BEZ == bez_id)
ggplot() + geom_path(aes(x=long, y=lat, group=group), data=lor[lor$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez, color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Blöcke im Bezirk

ggplot() + geom_path(aes(x=long, y=lat, group=group), data=blk[blk$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez[bez$BEZ == bez_id,], color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Kinder im Bezirk auf Blöcke hochrechnen

Über die Einwohnerinformationen in RBS_OD_BLK_2015_12.geojson kann EWR201512E_Matrix.csv von LOR-Ebene auf Blockebene hochgerechnet werden.

TODO: Stattdessen mit https://github.com/berlinermorgenpost/cogran machen?

Plot der 6-Jährigen nach EWR201512E_Matrix.csv

Wir verwenden das mittel der 5- und 6-Jährigen.

TODO neue Daten von Torres? TODO Prognose?

relevant_ewr = einwohner_lor %>%
  select(RAUMID, E_E05_06, E_E06_07) %>%
  filter(RAUMID %in% relevant_lors$PLR) %>%
  # Schnitt der 5 und 6-Jährigen
  mutate(kids=(as.numeric(gsub(',','.',E_E06_07))+as.numeric(gsub(',','.',E_E05_06)))/2) %>% as.data.frame()

data = tidy(lor[lor$BEZ == bez_id,], region='PLR') %>% inner_join(relevant_ewr, by=c('id'='RAUMID'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Plot der Einwohner auf Blockebene

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(df_blk, by=c('id'='BLK')) %>% mutate(Einw=ifelse(Einw==0, NA, Einw))
0
[1] 0
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=Einw), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Hochrrechnung auf Blöcke, Strukturquote

Strukturquote = 0.9

kids_in_blks = relevant_blks %>%
  group_by(PLR) %>%
  mutate(EinwRatio = Einw/sum(Einw)) %>%
  ungroup %>%
  left_join(relevant_ewr, by=c('PLR'='RAUMID')) %>%
  mutate(kids = EinwRatio*kids) %>%
  mutate(kids = Strukturquote*kids) %>% # Strukturquote
  select(BEZ, PLR, BLK, Einw, kids) %>%
  as.data.frame()
row.names(kids_in_blks) = kids_in_blks$BLK

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(kids_in_blks, by=c('id'='BLK')) %>% mutate(kids=ifelse(kids==0, NA, kids), Einw=ifelse(Einw==0, NA, Einw))

ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Verfügbare Plätze

relevant_kapas = kapas %>% select(Schulnummer, Kapa=`Plätze`) %>% filter(Schulnummer %in% relevant_schools) %>% as.data.frame()
#row.names(relevant_kapas) = relevant_kapas$Schulnummer

Überprüfung der Summe der Kapazitäten, Anmeldungen und Kinderstatistiken

'Summe Kapas'
[1] "Summe Kapas"
Summe Kapas
relevant_kapas %>% .$Kapa %>% sum
[1] 2584
'Anmeldungen'
[1] "Anmeldungen"
Anmeldungen
kapas %>% mutate(Anmeldungen = as.numeric(gsub('[^0-9]', '', Anmeldungen))) %>% filter(Schulnummer %in% relevant_schools) %>% .$Anmeldungen %>% sum
[1] 2752
'Kids laut Statistik'
[1] "Kids laut Statistik"
Kids laut Statistik
kids_in_blks$kids %>% sum
[1] 2620.8
relevant_ewr$kids %>% sum
[1] 2912

Schulwege von Blöcken zu Schulen aggregieren

Für jedes Wohngebäude suchen wir den zugehörigen Block

residential_buildings_blocks = sampled_buildings %>% inner_join(df_blk) %>% filter(BEZ == bez_id)
Joining, by = "BLK"
residential_buildings_blocks
routes_from_blks = residential_buildings_blocks %>%
  left_join(route_matrix %>% filter(dst %in% relevant_schools), by=c('OI'='src'))
head(routes_from_blks)

Plot der relevanten Blöcke (mit Wohngebäuden)

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(routes_from_blks %>% group_by(BLK) %>% summarise(n=n()), by=c('id'='BLK'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group), fill='red', data=data) +
  #geom_point(aes(x=lon, y=lat), data=rb_df, color='black', size=0.01) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_from_blks = routes_from_blks %>% as.data.frame() %>% group_by(BLK, dst) %>% summarise(min=min(distance), avg=mean(distance), med=median(distance), max=max(distance)) %>% ungroup
travel_from_blks

Plot der Blöcke mit Färbung nach durchschnittlichem Weg zur nächsten Schule

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(travel_from_blks %>% group_by(BLK) %>% top_n(1, -avg), by=c('id'='BLK'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=-avg), data=data) +
  geom_point(aes(lon, lat), color='red', data = re_schulstand_df %>% filter(BEZIRK==bezirk & SCHULART=='Grundschule')) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_matrix = travel_from_blks %>% select(BLK, dst, avg) %>% spread(dst, avg)
dim(travel_matrix)
[1] 1012   33
travel_matrix

Sozioökonomische Daten

Wir haben Sozioökonomische Daten in den Wahlbezirken. Strategie: - Schneiden der Wahlbezirke mit den Blöcken - Übernahme des Prozentwertes vom Wahlbezirk für jeden (Unter-)block - Zuordnung zu jedem Block und Vereinigung durch Flächen/Wohnhaus-gewichtetes Mittel des Prozentwertes

UWB = readOGR('download/RBS_OD_UWB_AGH2016.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_UWB_AGH2016.geojson", layer: "OGRGeoJSON"
with 1779 features
It has 4 fields
UWB$ID = paste0(UWB$BEZ, UWB$UWB)
sozio_UWB = read_excel('download/DL_BE_AH2016_Strukturdaten.xlsx', sheet = 3) %>% select(ID, sgbIIu65=`Einwohner unter 65 in SGB II 2014 Prozent`)
UWB = UWB %>% sp::merge(sozio_UWB, by='ID')

ggplot(broom::tidy(UWB, region='ID') %>% inner_join(UWB %>% as.data.frame, by=c('id'='ID'))) + geom_polygon(aes(x=long, y=lat, group=group, fill=sgbIIu65)) + coord_map()

Check for self intersections

wgs84 = CRS(proj4string(blk))
ea_projection = CRS("+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs")
blk_in_bez = blk[blk$BEZ == bez_id,]
gIsValid(blk_in_bez)
[1] TRUE
blk_in_bez = gBuffer(spTransform(blk_in_bez, ea_projection), byid=T, width = -0.1)
any(xor(gIntersects(blk_in_bez, byid=T, returnDense = T), diag(1, length(blk_in_bez)) == 1))
[1] FALSE
plot(blk_in_bez)

#gIntersects(UWB[UWB$BEZ == '07',], byid=TRUE)
uwb_in_bez = gBuffer(spTransform(UWB[UWB$BEZ == '07',], ea_projection), byid=T, width=-0.1)
gIsValid(uwb_in_bez)
[1] TRUE
any(xor(gIntersects(uwb_in_bez, byid=T, returnDense = T), diag(1, length(uwb_in_bez)) == 1))
[1] FALSE
plot(uwb_in_bez)

intersection = gIntersection(uwb_in_bez, blk_in_bez, byid = T, drop_lower_td = T)

gIsValid(intersection)
[1] TRUE
plot(intersection)

intersection_uwb_data = intersection %>% over(uwb_in_bez)
intersection_blk_data = intersection %>% over(blk_in_bez)
intersection_area = gArea(intersection, byid=T)
intersection_data = cbind(
    intersection_uwb_data %>% select(UWB_ID=ID, sgbIIu65),
    intersection_blk_data %>% select(BLK, BLK_Einw=Einw),
    data.frame(area=intersection_area) # FIXME how else to normalize?
    )
length(intersection)
[1] 1219
nrow(blk_in_bez)
[1] 1201
# did we miss any blocks?
setdiff(blk_in_bez$BLK, intersection_data$BLK)
character(0)

Pro Block mische die SGB-Werte der Unterblöcke gewichtet nach Fläche. FIXME - besser nach Anzahl der Wohnhäuser?

sgbII_blk = intersection_data %>%
  group_by(BLK) %>%
  summarise(sgbIIu65=sum(sgbIIu65*area)/sum(area)/100)
ggplot(broom::tidy(spTransform(blk_in_bez, wgs84), region='BLK') %>% left_join(sgbII_blk, by=c('id'='BLK'))) + geom_polygon(aes(x=long, y=lat, group=group, fill=sgbIIu65)) + coord_map()

Adjazenz von Blöcken

row.names(blk_in_bez) = blk_in_bez$BLK
buffeded_blk_in_bez = gBuffer(blk_in_bez, byid=T, width=40)
adjacency = gIntersects(gBuffer(blk_in_bez, byid=T, width=40), byid = T)
rownames(adjacency) = blk_in_bez$BLK
colnames(adjacency) = blk_in_bez$BLK

adjacency_df = adjacency %>% as.data.frame() %>% mutate(from=rownames(.)) %>%
  gather(to, connected, -from) %>% filter(connected) %>%
  inner_join(spTransform(blk_in_bez, wgs84) %>% coordinates() %>% as.data.frame() %>% rename(from_long=V1, from_lat=V2) %>% mutate(from=rownames(.))) %>%
  inner_join(spTransform(blk_in_bez, wgs84) %>% coordinates() %>% as.data.frame() %>% rename(to_long=V1, to_lat=V2) %>% mutate(to=rownames(.)))
Joining, by = "from"
Joining, by = "to"
ggplot() +
  geom_polygon(aes(x=long, y=lat, group=group), fill='gray', data=broom::tidy(spTransform(blk_in_bez, wgs84), region='BLK')) + 
  geom_segment(aes(x=from_long, y=from_lat, xend=to_long, yend=to_lat), size=0.1, color='black', data=adjacency_df) +
  theme_nothing() + coord_map()
Warning: `panel.margin` is deprecated. Please use `panel.spacing` property
instead

ggsave('figs/adjacency.pdf')
Saving 7 x 5 in image
adjacency_df %>% filter(connected & from != to) %>% select(from, to) %>% write_csv('app/data/adjacency.csv')

Relevante Daten auswählen

optim_kapas = relevant_kapas
optim_kids_in_blks = kids_in_blks %>% filter(kids > 0) %>% inner_join(travel_matrix, by='BLK') %>% select(BLK, kids) %>% mutate(kids=kids)
nrow(optim_kids_in_blks)
[1] 1012
nrow(optim_kapas)
[1] 32
select_schools = as.character(optim_kapas$Schulnummer)
select_blks = as.character(optim_kids_in_blks$BLK)

optim_matrix = inner_join(optim_kids_in_blks, travel_matrix, by='BLK')[select_schools]

dim(optim_matrix)
[1] 1012   32
optim_kapas$Kapa %>% sum
[1] 2584
optim_kids_in_blks$kids %>% sum
[1] 2611.995

Naive (initiale) Zuordnung: Jeder Block zur nächsten Schule

solution = optim_matrix %>% mutate(BLK=optim_kids_in_blks$BLK) %>% gather(school, dist, -BLK) %>% group_by(BLK) %>% top_n(1, -dist) %>% ungroup

optim_matrix %>% t %>% as.data.frame %>% summarise_each(funs(min)) %>% sum()
[1] 789029.5
solines = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school')) %>% inner_join(cbind(as.data.frame(coordinates(blk[blk$BEZ == bez_id,])), blk[blk$BEZ == bez_id,]@data['BLK']))
Joining, by = "BLK"
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(solution, by=c('id'='BLK'))
ggmap(map, darken = c(0.5, 'white')) + geom_polygon(aes(x=long, y=lat, group=group, fill=school), data=data) +
  geom_segment(aes(x=V1,y=V2,xend=lon,yend=lat), data=solines, size=0.3) +
  geom_point(aes(lon, lat), color='black', size=2, data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
  geom_point(aes(lon, lat, color=spatial_name), data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01)) +
  guides(color=F, fill=F)

Darstellung der Zuordnung als Tabelle

library(formattable)
solution %>% inner_join(optim_kids_in_blks, by='BLK') %>% inner_join(travel_from_blks, by=c('BLK'='BLK', 'school'='dst')) %>%
  group_by(school) %>% summarise(
    kids=sum(kids),
    num_blocks=n(),
    min_dist=min(min),
    avg_dist=mean((kids*avg)/sum(kids)),
    max_dist=max(max)
  ) %>%
  inner_join(relevant_kapas, by=c('school'='Schulnummer')) %>%
  mutate(
    utilization=kids/Kapa
  ) %>% select(
   Schule=school,
   `Blöcke`=num_blocks,
   Kapazität=Kapa,
   Kinder=kids,
   Auslastung=utilization,
   `Weg (min)`=min_dist,
   `Weg (Ø)`=avg_dist,
   `Weg (max)`=max_dist
  ) %>%
  formattable(
    list(
      Kinder = formatter("span", x ~ digits(x, 2)),
      Auslastung = formatter("span",
        style = x ~ style(color = ifelse(x < 1, "green", "red")),
        x ~ icontext(ifelse(x < 1, "ok", "remove"), percent(x))
      ),
      `Weg (Ø)` = proportion_bar("lightblue"),
      `Weg (min)` = proportion_bar("lightblue"),
      `Weg (max)` = proportion_bar("lightblue")
    )
  )
Schule Blöcke Kapazität Kinder Auslastung Weg (min) Weg (Ø) Weg (max)
07G01 15 66 74.18 112.39% 255.739151 632.0744 1396.8593
07G02 20 98 47.99 48.97% 230.477219 621.3752 1145.7729
07G03 16 73 60.89 83.41% 4.655441 439.9467 1180.3876
07G05 19 78 96.19 123.33% 107.536011 571.3078 1070.8099
07G06 26 55 71.27 129.58% 262.891296 795.8270 1395.3971
07G07 14 81 33.88 41.82% 132.709625 729.0923 1595.5302
07G10 29 112 130.44 116.46% 9.067926 621.7213 1521.6644
07G12 12 75 32.39 43.19% 19.516815 342.8940 670.5930
07G13 26 82 126.35 154.09% 36.279259 458.8285 1112.2148
07G14 32 78 80.91 103.73% 80.919022 418.8679 768.5562
07G15 48 104 202.25 194.48% 123.392151 818.0813 1615.6770
07G16 24 104 63.87 61.41% 75.267059 518.5303 1081.7244
07G17 24 100 70.80 70.80% 44.943558 362.8416 897.0190
07G18 16 44 48.82 110.95% 63.255936 337.0313 730.1954
07G19 36 112 108.22 96.62% 67.527443 1092.9489 2255.8521
07G20 40 81 139.24 171.90% 143.088776 746.6034 1633.1127
07G21 22 72 58.80 81.67% 84.344872 502.3471 1197.0938
07G22 26 91 60.77 66.78% 80.672195 611.8443 1479.7078
07G23 10 50 23.72 47.44% 70.703979 719.1343 1196.4078
07G24 30 75 88.71 118.28% 130.593018 680.2936 1933.3115
07G25 36 75 124.03 165.37% 41.979919 564.5368 1075.0486
07G26 24 52 30.61 58.86% 43.736134 558.2624 966.7025
07G27 20 75 50.27 67.02% 43.679813 670.4116 1385.6671
07G28 61 75 82.66 110.21% 107.804443 984.4391 2019.0012
07G29 98 104 119.37 114.78% 16.573118 957.5651 2051.8848
07G30 42 72 71.13 98.79% 58.391983 866.9357 2426.0039
07G31 19 78 45.03 57.73% 236.941177 886.2093 1706.3914
07G32 59 78 57.34 73.52% 57.186657 747.9164 1363.8368
07G34 42 100 179.21 179.21% 35.297066 1159.7623 2235.2996
07G35 33 75 87.59 116.79% 85.617516 747.5166 1482.5903
07G36 39 100 60.36 60.36% 137.395325 1021.0702 2236.2500
07G37 54 69 84.71 122.77% 109.733894 1290.1583 2135.0298

ndH und LMB-Daten (nicht offen)

ndh_lmb = read_excel('download/LMB_ndH2015.xlsx') %>% select(
  entity_id=BSN,
  ndH=`Anteil ndH`,
  LMB=`Anteil LMB`
)
ndh_lmb

Daten für die App speichern

Neue Daten

Entities / Schulen

entities = subset(re_schulstand_df, spatial_name %in% relevant_schools) %>%
  rename(entity_id = spatial_name) %>%
  select(-gml_id, -spatial_alias, -spatial_type) %>%
  inner_join(rename(relevant_kapas, capacity=Kapa), by=c('entity_id'='Schulnummer')) %>%
  left_join(ndh_lmb, by=c('entity_id'))
coordinates(entities) = ~ lon + lat
proj4string(entities) = CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
file.remove('app/data/entities.geojson')
[1] TRUE
entities %>% writeOGR('app/data/entities.geojson', layer="entities", driver="GeoJSON", check_exists=F)
entities@data %>% select(entity_id, capacity) %>% write_csv('app/data/entities.csv')

Units / Statistische Blöcke

units = subset(blk, BEZ == bez_id)

units@data$PLR = NULL
units@data$Einw = NULL
units@data$BEZ = NULL
units@data$unit_id = units@data$BLK
units@data$BLK = NULL

units = units %>% sp::merge(
  optim_kids_in_blks %>%
    left_join(sgbII_blk) %>%
    select(unit_id=BLK, population=kids, sgbIIu65)
  )
Joining, by = "BLK"
file.remove('app/data/units.geojson')
[1] TRUE
units %>% writeOGR('app/data/units.geojson', layer="entities", driver="GeoJSON", check_exists=F)
units@data %>% write_csv('app/data/units.csv')

Zuordnung

assignment = solution %>% select(unit_id=BLK, entity_id=school)
assignment %>% write_csv('app/data/assignment.csv')

Weights / Schulwege

travel_from_blks %>%
  rename(unit_id=BLK, entity_id=dst) %>%
  #gather(weight, value, -unit_id, -entity_id) %>%
  write_csv('app/data/weights.csv')

Zusätzliche Daten

file.copy('download/RBS_OD_BEZ_2015_12.geojson', 'app/data/RBS_OD_BEZ_2015_12.geojson')
[1] FALSE
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3IgbGlicywgaW5jbHVkZT1GLCB3YXJuaW5nPUZ9CmxpYnJhcnkocmVhZHIpCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KHJnZGFsKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dtYXApCmxpYnJhcnkocHVycnIpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkobWFwdG9vbHMpCmxpYnJhcnkocmdlb3MpCmBgYAoKRGllc2VzIE5vdGVib29rIGJlcmVpdGV0IGRpZSBEYXRlbiBmw7xyIGRpZSBJbnRlbGxpZ2VudCBab25pbmcgRW5naW5lIHZvci4gRXMgc3BlaWNoZXJ0CgotIGVudGl0aWVzLmdlb2pzb24g4oCUIFNjaHVsZW4sIGRlcmVuIEdlb2tvb3JkaW5hdGVuIHVuZCBBdHRyaWJ1dGU6IGVudGl0eV9pZCwgY2FwYWNpdHksIHVuZCBhbmRlcmUgQXR0cmlidXRlCi0gZW50aXRpZXMuY3N2IOKAlCBTdGF0aXN0aXNjaGUgQmzDtmNrZSBCZXJsaW5zIHVuZCBvcHRpbWllcnVuZ3NyZWxldmFudGUgQXR0cmlidXRlOiBlbnRpdHlfaWQsIGNhcGFjaXR5Ci0gdW5pdHMuZ2VvanNvbiDigJQgU3RhdGlzdGlzY2hlIEJsw7Zja2UgQmVybGlucywgZGVyZW4gR2VvbWV0cmllIHVuZCBBdHRyaWJ1dGUKLSB1bml0cy5jc3Yg4oCUIFN0YXRzaXRpc2NoZSBCbMO2Y2tlIEJlcmxpbnMgdW5kIG9wdGltaWVydW5nc3JlbGV2YW50ZSBBdHRyaWJ1dGU6IHVuaXRfaWQsIHBvcHVsYXRpb24sIHBlcmNlbnRhZ2Vfc2diCi0gd2VpZ2h0cy5jc3Yg4oCUIG9wdGltaWVydW5nc3JlbGV2YW50ZSBHZXdpY2h0ZSB3aWUgRnXDn3dlZ2UgLyBTcGFsdGVuOiBlbnRpdHlfaWQsIHVuaXRfaWQsIHdlaWdodCwgdmFsdWUKLSBhc3NpZ25tZW50LmNzdiDigJQgZWluZSBpbml0aWFsZSBadW9yZG51bmcgLyBTcGFsdGVuOiB1bml0X2lkLCBlbnRpdHlfaWQKCkJlcmVpdHMgaW4gYW5kZXJlbiBzY3JpcHRlbiB3dXJkZSB2b3JiZXJlaXRldDoKCi0gRnXDn3dlZ2VuIHZvbiBlaW5lciBncm/Dn2VuIFNpY2hwcm9iZSB2b24gKF9Xb2huXy0pR2Viw6R1ZGVuIHp1IGFsbGVuIFNjaHVsZW4gd3VyZGVuIGJlcmVjaG5ldCB1bmQgaW4gYHJvdXRlX21hdHJpeC5jc3ZgIGdlc3BlaWNoZXJ0Ci0gRGllIFN0aWNocHJvYmUgd3VyZGUgaW4gYHNhbXBsZWRfYnVpbGRpbmdzLmNzdmAgZ2VzcGVpY2hlcnQKCkRpZSBEYXRlbiB3ZXJkZW4gd2llZm9sZ3Qgdm9yYmVyZWl0ZXQ6CgotIHBybyBCbG9jayB3ZXJkZW4gZGllIEFuemFobCBkZXIgZWluenVzY2h1bGVuZGVuIEtpbmRlciBtaXQgSGlsZmUgZGVyIEVpbndvaG5lcnphaGxlbiBuYWNoIEFsdGVyIGF1ZiBMT1ItRWJlbmUgaW4gYEVXUjIwMTUxMkVfTWF0cml4LmNzdmAgaG9jaGdlcmVjaG5ldAogICAgLSBLaW5kZXIgZGVzIExPUiB3ZXJkZW4gQW50ZWlsaWcgbmFjaCBFaW53b2huZXJ6YWhsIGRlcyBCbG9ja3MgaW0gVmVyaMOkbHRuaXMgenVtIExPUiBhdWYgZGllIEJsw7Zja2UgdmVydGVpbHQKLSBlcyB3ZXJkZW4gbWluaW1hbGUsIGR1cmNoc2Nobml0dGxpY2hlIHVuZCBtYXhpbWFsZSBGdcOfd2VnZSBhdXMgamVkZW0gQmxvY2sgZXJyZWNobmV0CgpUT0RPOgotIGRpZSBzb3ppb8O2a29ub21pc2NoZW4gRmFrdG9yZW4gd2VyZGVuIGF1cyBkZW4gV2FobGJlemlya2VuIGF1ZiBkaWUgQmzDtmNrZSBob2NoZ2VyZWNobmV0IChodHRwczovL2dpdGh1Yi5jb20vYmVybGluZXJtb3JnZW5wb3N0L2NvZ3JhbikKCiMjIExhZGVuIGRlciBEYXRlbgoKYGBge3IgbG9hZCBkYXRhLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpzYW1wbGVkX2J1aWxkaW5ncyA9IHJlYWRfcmRzKCdvdXRwdXQvc2FtcGxlZF9idWlsZGluZ3MucmRzJykKYmV6ID0gcmVhZE9HUignZG93bmxvYWQvUkJTX09EX0JFWl8yMDE1XzEyLmdlb2pzb24nLCBsYXllciA9ICdPR1JHZW9KU09OJywgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpibGsgPSByZWFkT0dSKCdkb3dubG9hZC9SQlNfT0RfQkxLXzIwMTVfMTIuZ2VvanNvbicsIGxheWVyID0gJ09HUkdlb0pTT04nLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmxvciA9IHJlYWRPR1IoJ2Rvd25sb2FkL1JCU19PRF9MT1JfMjAxNV8xMi5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKcmVfc2NodWxzdGFuZCA9IHJlYWRPR1IoJ2Rvd25sb2FkL3JlX3NjaHVsc3RhbmQuZ2VvanNvbicsIGxheWVyID0gJ09HUkdlb0pTT04nLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmBgYAoKIyMjIFNjaHVsd2VnZQoKYGBge3J9CnJvdXRlX21hdHJpeCA9IHJlYWRfcmRzKCdvdXRwdXQvcm91dGVfbWF0cml4LnJkcycpCmBgYAoKIyMjIFNjaHVsa2FwYXppdMOkdGVuIHVuZCBFaW53b2huZXJ6YWhsZXIgYXVmIExPUi1FYmVuZQoKYGBge3J9CmthcGFzID0gcmVhZF9jc3YoJ2Rvd25sb2FkL2FubWVsZGV6YWhsZW4uY3N2JykgJT4lIGZpbHRlcihncmVwbCgnRycsIFNjaHVsbnVtbWVyKSkgJT4lIGZpbHRlcighaXMubmEoYFBsw6R0emVgKSkKZWlud29obmVyX2xvciA9IHJlYWRfZGVsaW0oJ2Rvd25sb2FkL0VXUjIwMTUxMkVfTWF0cml4LmNzdicsIGRlbGltPSc7JykKYGBgCgojIyDDnGJlcnByw7xmdW5nIGRlciBWb2xsc3TDpG5kaWdrZWl0IGRlciBEYXRlbiDDvGJlciBBbm1lbGRlemFobGVuL0thcGF6aXTDpHRlbgoKYGBge3J9CnJlX3NjaHVsc3RhbmRfZGYgPSByZV9zY2h1bHN0YW5kICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHJlbmFtZShsb249Y29vcmRzLngxLCBsYXQ9Y29vcmRzLngyKQpyZV9zY2h1bHN0YW5kX2RmICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgbXV0YXRlKEJFWklSSz1lbmMydXRmOChhcy5jaGFyYWN0ZXIoQkVaSVJLKSkpICU+JQogIGdyb3VwX2J5KEJFWklSSykgJT4lIHN1bW1hcmlzZShgQW56YWhsIFNjaHVsZW5gID0gbigpKSAlPiUKICByZW5hbWUoQmV6aXJrPUJFWklSSykgJT4lIGxlZnRfam9pbihrYXBhcyAlPiUgZ3JvdXBfYnkoQmV6aXJrKSAlPiUgc3VtbWFyaXNlKGBNaXQgS2FwYXppdMOkdGAgPSBuKCkpKQpgYGAKCkbDvHIgd2VsY2hlIEJlemlya2UgaGFiZW4gd2lyIGbDvHIgYWxsZSBTY2h1bGVuIEthcGF6aXTDpHRlbiBnZWdlYmVuPwoKYGBge3J9CnJlX3NjaHVsc3RhbmQgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIG11dGF0ZShCRVpJUks9ZW5jMnV0ZjgoYXMuY2hhcmFjdGVyKEJFWklSSykpKSAlPiUgZ3JvdXBfYnkoQkVaSVJLKSAlPiUgc3VtbWFyaXNlKGBBbnphaGwgU2NodWxlbmAgPSBuKCkpICU+JQogIHJlbmFtZShCZXppcms9QkVaSVJLKSAlPiUgbGVmdF9qb2luKGthcGFzICU+JSBncm91cF9ieShCZXppcmspICU+JSBzdW1tYXJpc2UoYE1pdCBLYXBheml0w6R0YCA9IG4oKSkpICU+JSBmaWx0ZXIoYEFuemFobCBTY2h1bGVuYCA9PSBgTWl0IEthcGF6aXTDpHRgKQpgYGAKCsOcYmVycHLDvGZ1bmcgb2IgZGllIExpc3RlIGRlciBTY2h1bGVuIHVuZCBMaXN0ZSBkZXIgU2NodWxlbiBtaXQgS2FwYXppdMOkdHNpbmZvcm1hdGlvbmVuIGdsZWljaCBzaW5kOgoKYGBge3J9CmJlemlyayA9ICdUZW1wZWxob2YtU2Now7ZuZWJlcmcnCnNjaHVsZW5fbWl0X2thcGEgPSBrYXBhcyAlPiUgZmlsdGVyKEJlemlyayA9PSBiZXppcmspICU+JSAuJFNjaHVsbnVtbWVyCnNjaHVsZW5fbWl0X2thcGEKZ3J1bmRzY2h1bGVuID0gcmVfc2NodWxzdGFuZCAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgZmlsdGVyKEJFWklSSyA9PSBiZXppcmspICU+JSAuJHNwYXRpYWxfbmFtZQonSW4gQW5tZWxkZWxpc3RlLCBmZWhsdCBpbiBTY2h1bHN0YW5kJwpzZXRkaWZmKHNjaHVsZW5fbWl0X2thcGEsIGdydW5kc2NodWxlbikKJ0luIHJlX3NjaHVsc3RhbmQsIGZlaGx0IGluIEFubWVsZGVsaXN0ZScKc2V0ZGlmZihncnVuZHNjaHVsZW4sIHNjaHVsZW5fbWl0X2thcGEpCmBgYAoKCmBgYHtyfQptYXAgPSBnZXRfbWFwKCdCZXJsaW4nKQpgYGAKCgpgYGB7cn0KcmVfc2NodWxzdGFuZF9kZl93X2thcGFzID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgbGVmdF9qb2luKGthcGFzLCBieT1jKCdzcGF0aWFsX25hbWUnPSdTY2h1bG51bW1lcicpKQpgYGAKClBsb3QgYWxsZXIgU2NodWxlbiwgbWl0IGRlciBJbmZvLCBvYiBLYXBheml0w6R0c2luZm9ybWF0aW9uZW4gdmVyZsO8Z2JhciBzaW5kLgoKYGBge3J9CmRhdGEgPSByZV9zY2h1bHN0YW5kX2RmX3dfa2FwYXMgJT4lIGZpbHRlcihncmVwbCgnRycsIHNwYXRpYWxfbmFtZSkpICU+JSBmaWx0ZXIoQkVaSVJLPT1iZXppcmspICU+JSBtdXRhdGUobWlzc2luZy5jYXBhPWlzLm5hKGBQbMOkdHplYCkpCmdnbWFwKG1hcCkgKyBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCwgY29sb3I9bWlzc2luZy5jYXBhKSwgZGF0YT1kYXRhKSArCiAgICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbiktMC4wMSwgbWF4KGRhdGEkbG9uKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgpGaWx0ZXIgYXVmIFNjaHVsZW4gbWl0IEthcGF6aXTDpHRzaW5mb3JtYXRpb25lbiAoZsO8ciBULVMgc2luZCBkYXMgYWxsZSk6CgpgYGB7cn0KcmVsZXZhbnRfc2Nob29scyA9IHJlX3NjaHVsc3RhbmRfZGZfd19rYXBhcyAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIGZpbHRlcihCRVpJUks9PWJlemlyayAmICFpcy5uYShgUGzDpHR6ZWApKSAlPiUgLiRzcGF0aWFsX25hbWUKcmVsZXZhbnRfc2Nob29scwpgYGAKCiMjIE1hcHBpbmcgQmV6aXJrLT5MT1ItPkJsb2NrCgpgYGB7cn0KZGZfYmV6ID0gYXMuZGF0YS5mcmFtZShiZXopCmRmX2xvciA9IGFzLmRhdGEuZnJhbWUobG9yKQpkZl9ibGsgPSBhcy5kYXRhLmZyYW1lKGJsaykKYGBgCgojIyMgU2FuaXR5LUNoZWNrOiBMT1JzIHVuZCBCbMO2Y2tlIGltIEJlemlyawoKYGBge3J9CmJlel9pZCA9IGZpbHRlcihkZl9iZXosIEJFWk5BTUUgPT0gYmV6aXJrKSRCRVoKcmVsZXZhbnRfbG9ycyA9IGRmX2xvciAlPiUgZmlsdGVyKEJFWiA9PSBiZXpfaWQpCnJlbGV2YW50X2Jsa3MgPSBkZl9ibGsgJT4lIGZpbHRlcihCRVogPT0gYmV6X2lkKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWxvcltsb3IkQkVaID09IGJlel9pZCxdKSArIGNvb3JkX21hcCgpICsgZ2VvbV9wYXRoKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGRhdGE9YmV6LCBjb2xvcj0ncmVkJykKYGBgCgojIyMgQmzDtmNrZSBpbSBCZXppcmsKCmBgYHtyfQpnZ3Bsb3QoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWJsa1tibGskQkVaID09IGJlel9pZCxdKSArIGNvb3JkX21hcCgpICsgZ2VvbV9wYXRoKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGRhdGE9YmV6W2JleiRCRVogPT0gYmV6X2lkLF0sIGNvbG9yPSdyZWQnKQpgYGAKCiMjIEtpbmRlciBpbSBCZXppcmsgYXVmIEJsw7Zja2UgaG9jaHJlY2huZW4KCsOcYmVyIGRpZSBFaW53b2huZXJpbmZvcm1hdGlvbmVuIGluIGBSQlNfT0RfQkxLXzIwMTVfMTIuZ2VvanNvbmAga2FubiBgRVdSMjAxNTEyRV9NYXRyaXguY3N2YCB2b24gTE9SLUViZW5lIGF1ZiBCbG9ja2ViZW5lIGhvY2hnZXJlY2huZXQgd2VyZGVuLgoKVE9ETzogU3RhdHRkZXNzZW4gbWl0IGh0dHBzOi8vZ2l0aHViLmNvbS9iZXJsaW5lcm1vcmdlbnBvc3QvY29ncmFuIG1hY2hlbj8KCiMjIyBQbG90IGRlciA2LUrDpGhyaWdlbiBuYWNoIGBFV1IyMDE1MTJFX01hdHJpeC5jc3ZgCgpXaXIgdmVyd2VuZGVuIGRhcyBtaXR0ZWwgZGVyIDUtIHVuZCA2LUrDpGhyaWdlbi4KClRPRE8gbmV1ZSBEYXRlbiB2b24gVG9ycmVzPwpUT0RPIFByb2dub3NlPwoKYGBge3J9CnJlbGV2YW50X2V3ciA9IGVpbndvaG5lcl9sb3IgJT4lCiAgc2VsZWN0KFJBVU1JRCwgRV9FMDVfMDYsIEVfRTA2XzA3KSAlPiUKICBmaWx0ZXIoUkFVTUlEICVpbiUgcmVsZXZhbnRfbG9ycyRQTFIpICU+JQogICMgU2Nobml0dCBkZXIgNSB1bmQgNi1Kw6RocmlnZW4KICBtdXRhdGUoa2lkcz0oYXMubnVtZXJpYyhnc3ViKCcsJywnLicsRV9FMDZfMDcpKSthcy5udW1lcmljKGdzdWIoJywnLCcuJyxFX0UwNV8wNikpKS8yKSAlPiUgYXMuZGF0YS5mcmFtZSgpCgpkYXRhID0gdGlkeShsb3JbbG9yJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdQTFInKSAlPiUgaW5uZXJfam9pbihyZWxldmFudF9ld3IsIGJ5PWMoJ2lkJz0nUkFVTUlEJykpCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPWtpZHMpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCiMjIyBQbG90IGRlciBFaW53b2huZXIgYXVmIEJsb2NrZWJlbmUKCmBgYHtyfQpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgaW5uZXJfam9pbihkZl9ibGssIGJ5PWMoJ2lkJz0nQkxLJykpICU+JSBtdXRhdGUoRWludz1pZmVsc2UoRWludz09MCwgTkEsIEVpbncpKQowCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPUVpbncpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCgojIyMgSG9jaHJyZWNobnVuZyBhdWYgQmzDtmNrZSwgU3RydWt0dXJxdW90ZQoKYGBge3J9ClN0cnVrdHVycXVvdGUgPSAwLjkKCmtpZHNfaW5fYmxrcyA9IHJlbGV2YW50X2Jsa3MgJT4lCiAgZ3JvdXBfYnkoUExSKSAlPiUKICBtdXRhdGUoRWlud1JhdGlvID0gRWludy9zdW0oRWludykpICU+JQogIHVuZ3JvdXAgJT4lCiAgbGVmdF9qb2luKHJlbGV2YW50X2V3ciwgYnk9YygnUExSJz0nUkFVTUlEJykpICU+JQogIG11dGF0ZShraWRzID0gRWlud1JhdGlvKmtpZHMpICU+JQogIG11dGF0ZShraWRzID0gU3RydWt0dXJxdW90ZSpraWRzKSAlPiUgIyBTdHJ1a3R1cnF1b3RlCiAgc2VsZWN0KEJFWiwgUExSLCBCTEssIEVpbncsIGtpZHMpICU+JQogIGFzLmRhdGEuZnJhbWUoKQpyb3cubmFtZXMoa2lkc19pbl9ibGtzKSA9IGtpZHNfaW5fYmxrcyRCTEsKCmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBpbm5lcl9qb2luKGtpZHNfaW5fYmxrcywgYnk9YygnaWQnPSdCTEsnKSkgJT4lIG11dGF0ZShraWRzPWlmZWxzZShraWRzPT0wLCBOQSwga2lkcyksIEVpbnc9aWZlbHNlKEVpbnc9PTAsIE5BLCBFaW53KSkKCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPWtpZHMpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCiMjIFZlcmbDvGdiYXJlIFBsw6R0emUKCmBgYHtyfQpyZWxldmFudF9rYXBhcyA9IGthcGFzICU+JSBzZWxlY3QoU2NodWxudW1tZXIsIEthcGE9YFBsw6R0emVgKSAlPiUgZmlsdGVyKFNjaHVsbnVtbWVyICVpbiUgcmVsZXZhbnRfc2Nob29scykgJT4lIGFzLmRhdGEuZnJhbWUoKQojcm93Lm5hbWVzKHJlbGV2YW50X2thcGFzKSA9IHJlbGV2YW50X2thcGFzJFNjaHVsbnVtbWVyCmBgYAoKIyMjIMOcYmVycHLDvGZ1bmcgZGVyIFN1bW1lIGRlciBLYXBheml0w6R0ZW4sIEFubWVsZHVuZ2VuIHVuZCBLaW5kZXJzdGF0aXN0aWtlbgoKYGBge3J9CidTdW1tZSBLYXBhcycKcmVsZXZhbnRfa2FwYXMgJT4lIC4kS2FwYSAlPiUgc3VtCidBbm1lbGR1bmdlbicKa2FwYXMgJT4lIG11dGF0ZShBbm1lbGR1bmdlbiA9IGFzLm51bWVyaWMoZ3N1YignW14wLTldJywgJycsIEFubWVsZHVuZ2VuKSkpICU+JSBmaWx0ZXIoU2NodWxudW1tZXIgJWluJSByZWxldmFudF9zY2hvb2xzKSAlPiUgLiRBbm1lbGR1bmdlbiAlPiUgc3VtCidLaWRzIGxhdXQgU3RhdGlzdGlrJwpraWRzX2luX2Jsa3Mka2lkcyAlPiUgc3VtCnJlbGV2YW50X2V3ciRraWRzICU+JSBzdW0KYGBgCgojIyBTY2h1bHdlZ2Ugdm9uIEJsw7Zja2VuIHp1IFNjaHVsZW4gYWdncmVnaWVyZW4KCkbDvHIgamVkZXMgV29obmdlYsOkdWRlIHN1Y2hlbiB3aXIgZGVuIHp1Z2Vow7ZyaWdlbiBCbG9jawoKYGBge3J9CnJlc2lkZW50aWFsX2J1aWxkaW5nc19ibG9ja3MgPSBzYW1wbGVkX2J1aWxkaW5ncyAlPiUgaW5uZXJfam9pbihkZl9ibGspICU+JSBmaWx0ZXIoQkVaID09IGJlel9pZCkKcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcwpgYGAKCmBgYHtyfQpyb3V0ZXNfZnJvbV9ibGtzID0gcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcyAlPiUKICBsZWZ0X2pvaW4ocm91dGVfbWF0cml4ICU+JSBmaWx0ZXIoZHN0ICVpbiUgcmVsZXZhbnRfc2Nob29scyksIGJ5PWMoJ09JJz0nc3JjJykpCmhlYWQocm91dGVzX2Zyb21fYmxrcykKYGBgCgojIyMgUGxvdCBkZXIgcmVsZXZhbnRlbiBCbMO2Y2tlIChtaXQgV29obmdlYsOkdWRlbikKCmBgYHtyfQpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgaW5uZXJfam9pbihyb3V0ZXNfZnJvbV9ibGtzICU+JSBncm91cF9ieShCTEspICU+JSBzdW1tYXJpc2Uobj1uKCkpLCBieT1jKCdpZCc9J0JMSycpKQpnZ21hcChtYXApICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGZpbGw9J3JlZCcsIGRhdGE9ZGF0YSkgKwogICNnZW9tX3BvaW50KGFlcyh4PWxvbiwgeT1sYXQpLCBkYXRhPXJiX2RmLCBjb2xvcj0nYmxhY2snLCBzaXplPTAuMDEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCgpgYGB7cn0KdHJhdmVsX2Zyb21fYmxrcyA9IHJvdXRlc19mcm9tX2Jsa3MgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZ3JvdXBfYnkoQkxLLCBkc3QpICU+JSBzdW1tYXJpc2UobWluPW1pbihkaXN0YW5jZSksIGF2Zz1tZWFuKGRpc3RhbmNlKSwgbWVkPW1lZGlhbihkaXN0YW5jZSksIG1heD1tYXgoZGlzdGFuY2UpKSAlPiUgdW5ncm91cAp0cmF2ZWxfZnJvbV9ibGtzCmBgYAoKIyMjIFBsb3QgZGVyIEJsw7Zja2UgbWl0IEbDpHJidW5nIG5hY2ggZHVyY2hzY2huaXR0bGljaGVtIFdlZyB6dXIgbsOkY2hzdGVuIFNjaHVsZQoKYGBge3J9CmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBsZWZ0X2pvaW4odHJhdmVsX2Zyb21fYmxrcyAlPiUgZ3JvdXBfYnkoQkxLKSAlPiUgdG9wX24oMSwgLWF2ZyksIGJ5PWMoJ2lkJz0nQkxLJykpCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPS1hdmcpLCBkYXRhPWRhdGEpICsKICBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCksIGNvbG9yPSdyZWQnLCBkYXRhID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgZmlsdGVyKEJFWklSSz09YmV6aXJrICYgU0NIVUxBUlQ9PSdHcnVuZHNjaHVsZScpKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgoKYGBge3J9CnRyYXZlbF9tYXRyaXggPSB0cmF2ZWxfZnJvbV9ibGtzICU+JSBzZWxlY3QoQkxLLCBkc3QsIGF2ZykgJT4lIHNwcmVhZChkc3QsIGF2ZykKZGltKHRyYXZlbF9tYXRyaXgpCnRyYXZlbF9tYXRyaXgKYGBgCgojIyBTb3ppb8O2a29ub21pc2NoZSBEYXRlbgoKV2lyIGhhYmVuIFNvemlvw7Zrb25vbWlzY2hlIERhdGVuIGluIGRlbiBXYWhsYmV6aXJrZW4uIFN0cmF0ZWdpZToKLSBTY2huZWlkZW4gZGVyIFdhaGxiZXppcmtlIG1pdCBkZW4gQmzDtmNrZW4KLSDDnGJlcm5haG1lIGRlcyBQcm96ZW50d2VydGVzIHZvbSBXYWhsYmV6aXJrIGbDvHIgamVkZW4gKFVudGVyLSlibG9jawotIFp1b3JkbnVuZyB6dSBqZWRlbSBCbG9jayB1bmQgVmVyZWluaWd1bmcgZHVyY2ggRmzDpGNoZW4vV29obmhhdXMtZ2V3aWNodGV0ZXMgTWl0dGVsIGRlcyBQcm96ZW50d2VydGVzIAoKYGBge3J9ClVXQiA9IHJlYWRPR1IoJ2Rvd25sb2FkL1JCU19PRF9VV0JfQUdIMjAxNi5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKVVdCJElEID0gcGFzdGUwKFVXQiRCRVosIFVXQiRVV0IpCnNvemlvX1VXQiA9IHJlYWRfZXhjZWwoJ2Rvd25sb2FkL0RMX0JFX0FIMjAxNl9TdHJ1a3R1cmRhdGVuLnhsc3gnLCBzaGVldCA9IDMpICU+JSBzZWxlY3QoSUQsIHNnYklJdTY1PWBFaW53b2huZXIgdW50ZXIgNjUgaW4gU0dCIElJIDIwMTQgUHJvemVudGApClVXQiA9IFVXQiAlPiUgc3A6Om1lcmdlKHNvemlvX1VXQiwgYnk9J0lEJykKCmdncGxvdChicm9vbTo6dGlkeShVV0IsIHJlZ2lvbj0nSUQnKSAlPiUgaW5uZXJfam9pbihVV0IgJT4lIGFzLmRhdGEuZnJhbWUsIGJ5PWMoJ2lkJz0nSUQnKSkpICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1zZ2JJSXU2NSkpICsgY29vcmRfbWFwKCkKYGBgCgpDaGVjayBmb3Igc2VsZiBpbnRlcnNlY3Rpb25zCmBgYHtyfQp3Z3M4NCA9IENSUyhwcm9qNHN0cmluZyhibGspKQplYV9wcm9qZWN0aW9uID0gQ1JTKCIrcHJvaj1sYWVhICtsYXRfMD01MiArbG9uXzA9MTAgK3hfMD00MzIxMDAwICt5XzA9MzIxMDAwMCArZWxscHM9R1JTODAgK3Rvd2dzODQ9MCwwLDAsMCwwLDAsMCArdW5pdHM9bSArbm9fZGVmcyIpCmJsa19pbl9iZXogPSBibGtbYmxrJEJFWiA9PSBiZXpfaWQsXQpnSXNWYWxpZChibGtfaW5fYmV6KQpibGtfaW5fYmV6ID0gZ0J1ZmZlcihzcFRyYW5zZm9ybShibGtfaW5fYmV6LCBlYV9wcm9qZWN0aW9uKSwgYnlpZD1ULCB3aWR0aCA9IC0wLjEpCmFueSh4b3IoZ0ludGVyc2VjdHMoYmxrX2luX2JleiwgYnlpZD1ULCByZXR1cm5EZW5zZSA9IFQpLCBkaWFnKDEsIGxlbmd0aChibGtfaW5fYmV6KSkgPT0gMSkpCnBsb3QoYmxrX2luX2JleikKI2dJbnRlcnNlY3RzKFVXQltVV0IkQkVaID09ICcwNycsXSwgYnlpZD1UUlVFKQpgYGAKCmBgYHtyfQp1d2JfaW5fYmV6ID0gZ0J1ZmZlcihzcFRyYW5zZm9ybShVV0JbVVdCJEJFWiA9PSAnMDcnLF0sIGVhX3Byb2plY3Rpb24pLCBieWlkPVQsIHdpZHRoPS0wLjEpCmdJc1ZhbGlkKHV3Yl9pbl9iZXopCmFueSh4b3IoZ0ludGVyc2VjdHModXdiX2luX2JleiwgYnlpZD1ULCByZXR1cm5EZW5zZSA9IFQpLCBkaWFnKDEsIGxlbmd0aCh1d2JfaW5fYmV6KSkgPT0gMSkpCnBsb3QodXdiX2luX2JleikKYGBgCgpgYGB7cn0KaW50ZXJzZWN0aW9uID0gZ0ludGVyc2VjdGlvbih1d2JfaW5fYmV6LCBibGtfaW5fYmV6LCBieWlkID0gVCwgZHJvcF9sb3dlcl90ZCA9IFQpCgpnSXNWYWxpZChpbnRlcnNlY3Rpb24pCnBsb3QoaW50ZXJzZWN0aW9uKQpgYGAKCmBgYHtyfQppbnRlcnNlY3Rpb25fdXdiX2RhdGEgPSBpbnRlcnNlY3Rpb24gJT4lIG92ZXIodXdiX2luX2JleikKaW50ZXJzZWN0aW9uX2Jsa19kYXRhID0gaW50ZXJzZWN0aW9uICU+JSBvdmVyKGJsa19pbl9iZXopCmludGVyc2VjdGlvbl9hcmVhID0gZ0FyZWEoaW50ZXJzZWN0aW9uLCBieWlkPVQpCmludGVyc2VjdGlvbl9kYXRhID0gY2JpbmQoCiAgICBpbnRlcnNlY3Rpb25fdXdiX2RhdGEgJT4lIHNlbGVjdChVV0JfSUQ9SUQsIHNnYklJdTY1KSwKICAgIGludGVyc2VjdGlvbl9ibGtfZGF0YSAlPiUgc2VsZWN0KEJMSywgQkxLX0Vpbnc9RWludyksCiAgICBkYXRhLmZyYW1lKGFyZWE9aW50ZXJzZWN0aW9uX2FyZWEpICMgRklYTUUgaG93IGVsc2UgdG8gbm9ybWFsaXplPwogICAgKQpgYGAKCmBgYHtyfQpsZW5ndGgoaW50ZXJzZWN0aW9uKQpucm93KGJsa19pbl9iZXopCiMgZGlkIHdlIG1pc3MgYW55IGJsb2Nrcz8Kc2V0ZGlmZihibGtfaW5fYmV6JEJMSywgaW50ZXJzZWN0aW9uX2RhdGEkQkxLKQpgYGAKClBybyBCbG9jayBtaXNjaGUgZGllIFNHQi1XZXJ0ZSBkZXIgVW50ZXJibMO2Y2tlIGdld2ljaHRldCBuYWNoIEZsw6RjaGUuCkZJWE1FIC0gYmVzc2VyIG5hY2ggQW56YWhsIGRlciBXb2huaMOkdXNlcj8KYGBge3J9CnNnYklJX2JsayA9IGludGVyc2VjdGlvbl9kYXRhICU+JQogIGdyb3VwX2J5KEJMSykgJT4lCiAgc3VtbWFyaXNlKHNnYklJdTY1PXN1bShzZ2JJSXU2NSphcmVhKS9zdW0oYXJlYSkvMTAwKQpgYGAKCgpgYGB7cn0KZ2dwbG90KGJyb29tOjp0aWR5KHNwVHJhbnNmb3JtKGJsa19pbl9iZXosIHdnczg0KSwgcmVnaW9uPSdCTEsnKSAlPiUgbGVmdF9qb2luKHNnYklJX2JsaywgYnk9YygnaWQnPSdCTEsnKSkpICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1zZ2JJSXU2NSkpICsgY29vcmRfbWFwKCkKYGBgCgojIyBBZGphemVueiB2b24gQmzDtmNrZW4KCmBgYHtyfQpyb3cubmFtZXMoYmxrX2luX2JleikgPSBibGtfaW5fYmV6JEJMSwpidWZmZWRlZF9ibGtfaW5fYmV6ID0gZ0J1ZmZlcihibGtfaW5fYmV6LCBieWlkPVQsIHdpZHRoPTQwKQphZGphY2VuY3kgPSBnSW50ZXJzZWN0cyhnQnVmZmVyKGJsa19pbl9iZXosIGJ5aWQ9VCwgd2lkdGg9NDApLCBieWlkID0gVCkKcm93bmFtZXMoYWRqYWNlbmN5KSA9IGJsa19pbl9iZXokQkxLCmNvbG5hbWVzKGFkamFjZW5jeSkgPSBibGtfaW5fYmV6JEJMSwoKYWRqYWNlbmN5X2RmID0gYWRqYWNlbmN5ICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIG11dGF0ZShmcm9tPXJvd25hbWVzKC4pKSAlPiUKICBnYXRoZXIodG8sIGNvbm5lY3RlZCwgLWZyb20pICU+JSBmaWx0ZXIoY29ubmVjdGVkKSAlPiUKICBpbm5lcl9qb2luKHNwVHJhbnNmb3JtKGJsa19pbl9iZXosIHdnczg0KSAlPiUgY29vcmRpbmF0ZXMoKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSByZW5hbWUoZnJvbV9sb25nPVYxLCBmcm9tX2xhdD1WMikgJT4lIG11dGF0ZShmcm9tPXJvd25hbWVzKC4pKSkgJT4lCiAgaW5uZXJfam9pbihzcFRyYW5zZm9ybShibGtfaW5fYmV6LCB3Z3M4NCkgJT4lIGNvb3JkaW5hdGVzKCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgcmVuYW1lKHRvX2xvbmc9VjEsIHRvX2xhdD1WMikgJT4lIG11dGF0ZSh0bz1yb3duYW1lcyguKSkpCgpnZ3Bsb3QoKSArCiAgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGZpbGw9J2dyYXknLCBkYXRhPWJyb29tOjp0aWR5KHNwVHJhbnNmb3JtKGJsa19pbl9iZXosIHdnczg0KSwgcmVnaW9uPSdCTEsnKSkgKyAKICBnZW9tX3NlZ21lbnQoYWVzKHg9ZnJvbV9sb25nLCB5PWZyb21fbGF0LCB4ZW5kPXRvX2xvbmcsIHllbmQ9dG9fbGF0KSwgc2l6ZT0wLjEsIGNvbG9yPSdibGFjaycsIGRhdGE9YWRqYWNlbmN5X2RmKSArCiAgdGhlbWVfbm90aGluZygpICsgY29vcmRfbWFwKCkKCmdnc2F2ZSgnZmlncy9hZGphY2VuY3kucGRmJykKYWRqYWNlbmN5X2RmICU+JSBmaWx0ZXIoY29ubmVjdGVkICYgZnJvbSAhPSB0bykgJT4lIHNlbGVjdChmcm9tLCB0bykgJT4lIHdyaXRlX2NzdignYXBwL2RhdGEvYWRqYWNlbmN5LmNzdicpCmBgYAoKCiMjIFJlbGV2YW50ZSBEYXRlbiBhdXN3w6RobGVuCgpgYGB7cn0Kb3B0aW1fa2FwYXMgPSByZWxldmFudF9rYXBhcwpvcHRpbV9raWRzX2luX2Jsa3MgPSBraWRzX2luX2Jsa3MgJT4lIGZpbHRlcihraWRzID4gMCkgJT4lIGlubmVyX2pvaW4odHJhdmVsX21hdHJpeCwgYnk9J0JMSycpICU+JSBzZWxlY3QoQkxLLCBraWRzKSAlPiUgbXV0YXRlKGtpZHM9a2lkcykKbnJvdyhvcHRpbV9raWRzX2luX2Jsa3MpCm5yb3cob3B0aW1fa2FwYXMpCgpzZWxlY3Rfc2Nob29scyA9IGFzLmNoYXJhY3RlcihvcHRpbV9rYXBhcyRTY2h1bG51bW1lcikKc2VsZWN0X2Jsa3MgPSBhcy5jaGFyYWN0ZXIob3B0aW1fa2lkc19pbl9ibGtzJEJMSykKCm9wdGltX21hdHJpeCA9IGlubmVyX2pvaW4ob3B0aW1fa2lkc19pbl9ibGtzLCB0cmF2ZWxfbWF0cml4LCBieT0nQkxLJylbc2VsZWN0X3NjaG9vbHNdCgpkaW0ob3B0aW1fbWF0cml4KQoKb3B0aW1fa2FwYXMkS2FwYSAlPiUgc3VtCm9wdGltX2tpZHNfaW5fYmxrcyRraWRzICU+JSBzdW0KYGBgCgojIyBOYWl2ZSAoaW5pdGlhbGUpIFp1b3JkbnVuZzogSmVkZXIgQmxvY2sgenVyIG7DpGNoc3RlbiBTY2h1bGUKCmBgYHtyfQpzb2x1dGlvbiA9IG9wdGltX21hdHJpeCAlPiUgbXV0YXRlKEJMSz1vcHRpbV9raWRzX2luX2Jsa3MkQkxLKSAlPiUgZ2F0aGVyKHNjaG9vbCwgZGlzdCwgLUJMSykgJT4lIGdyb3VwX2J5KEJMSykgJT4lIHRvcF9uKDEsIC1kaXN0KSAlPiUgdW5ncm91cAoKb3B0aW1fbWF0cml4ICU+JSB0ICU+JSBhcy5kYXRhLmZyYW1lICU+JSBzdW1tYXJpc2VfZWFjaChmdW5zKG1pbikpICU+JSBzdW0oKQoKc29saW5lcyA9IHJlX3NjaHVsc3RhbmRfZGYgJT4lIGlubmVyX2pvaW4oc29sdXRpb24sIGJ5PWMoJ3NwYXRpYWxfbmFtZSc9J3NjaG9vbCcpKSAlPiUgaW5uZXJfam9pbihjYmluZChhcy5kYXRhLmZyYW1lKGNvb3JkaW5hdGVzKGJsa1tibGskQkVaID09IGJlel9pZCxdKSksIGJsa1tibGskQkVaID09IGJlel9pZCxdQGRhdGFbJ0JMSyddKSkKCmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBsZWZ0X2pvaW4oc29sdXRpb24sIGJ5PWMoJ2lkJz0nQkxLJykpCmdnbWFwKG1hcCwgZGFya2VuID0gYygwLjUsICd3aGl0ZScpKSArIGdlb21fcG9seWdvbihhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXAsIGZpbGw9c2Nob29sKSwgZGF0YT1kYXRhKSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4PVYxLHk9VjIseGVuZD1sb24seWVuZD1sYXQpLCBkYXRhPXNvbGluZXMsIHNpemU9MC4zKSArCiAgZ2VvbV9wb2ludChhZXMobG9uLCBsYXQpLCBjb2xvcj0nYmxhY2snLCBzaXplPTIsIGRhdGEgPSByZV9zY2h1bHN0YW5kX2RmICU+JSBpbm5lcl9qb2luKHNvbHV0aW9uLCBieT1jKCdzcGF0aWFsX25hbWUnPSdzY2hvb2wnKSkpICsKICBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCwgY29sb3I9c3BhdGlhbF9uYW1lKSwgZGF0YSA9IHJlX3NjaHVsc3RhbmRfZGYgJT4lIGlubmVyX2pvaW4oc29sdXRpb24sIGJ5PWMoJ3NwYXRpYWxfbmFtZSc9J3NjaG9vbCcpKSkgKwogIGNvb3JkX21hcCh4bGltPWMobWluKGRhdGEkbG9uZyktMC4wMSwgbWF4KGRhdGEkbG9uZykrMC4wMSksIHlsaW09YyhtaW4oZGF0YSRsYXQpLTAuMDEsIG1heChkYXRhJGxhdCkrMC4wMSkpICsKICBndWlkZXMoY29sb3I9RiwgZmlsbD1GKQpgYGAKCiMjIERhcnN0ZWxsdW5nIGRlciBadW9yZG51bmcgYWxzIFRhYmVsbGUKCmBgYHtyfQpsaWJyYXJ5KGZvcm1hdHRhYmxlKQpgYGAKCmBgYHtyfQpzb2x1dGlvbiAlPiUgaW5uZXJfam9pbihvcHRpbV9raWRzX2luX2Jsa3MsIGJ5PSdCTEsnKSAlPiUgaW5uZXJfam9pbih0cmF2ZWxfZnJvbV9ibGtzLCBieT1jKCdCTEsnPSdCTEsnLCAnc2Nob29sJz0nZHN0JykpICU+JQogIGdyb3VwX2J5KHNjaG9vbCkgJT4lIHN1bW1hcmlzZSgKICAgIGtpZHM9c3VtKGtpZHMpLAogICAgbnVtX2Jsb2Nrcz1uKCksCiAgICBtaW5fZGlzdD1taW4obWluKSwKICAgIGF2Z19kaXN0PW1lYW4oKGtpZHMqYXZnKS9zdW0oa2lkcykpLAogICAgbWF4X2Rpc3Q9bWF4KG1heCkKICApICU+JQogIGlubmVyX2pvaW4ocmVsZXZhbnRfa2FwYXMsIGJ5PWMoJ3NjaG9vbCc9J1NjaHVsbnVtbWVyJykpICU+JQogIG11dGF0ZSgKICAgIHV0aWxpemF0aW9uPWtpZHMvS2FwYQogICkgJT4lIHNlbGVjdCgKICAgU2NodWxlPXNjaG9vbCwKICAgYEJsw7Zja2VgPW51bV9ibG9ja3MsCiAgIEthcGF6aXTDpHQ9S2FwYSwKICAgS2luZGVyPWtpZHMsCiAgIEF1c2xhc3R1bmc9dXRpbGl6YXRpb24sCiAgIGBXZWcgKG1pbilgPW1pbl9kaXN0LAogICBgV2VnICjDmClgPWF2Z19kaXN0LAogICBgV2VnIChtYXgpYD1tYXhfZGlzdAogICkgJT4lCiAgZm9ybWF0dGFibGUoCiAgICBsaXN0KAogICAgICBLaW5kZXIgPSBmb3JtYXR0ZXIoInNwYW4iLCB4IH4gZGlnaXRzKHgsIDIpKSwKICAgICAgQXVzbGFzdHVuZyA9IGZvcm1hdHRlcigic3BhbiIsCiAgICAgICAgc3R5bGUgPSB4IH4gc3R5bGUoY29sb3IgPSBpZmVsc2UoeCA8IDEsICJncmVlbiIsICJyZWQiKSksCiAgICAgICAgeCB+IGljb250ZXh0KGlmZWxzZSh4IDwgMSwgIm9rIiwgInJlbW92ZSIpLCBwZXJjZW50KHgpKQogICAgICApLAogICAgICBgV2VnICjDmClgID0gcHJvcG9ydGlvbl9iYXIoImxpZ2h0Ymx1ZSIpLAogICAgICBgV2VnIChtaW4pYCA9IHByb3BvcnRpb25fYmFyKCJsaWdodGJsdWUiKSwKICAgICAgYFdlZyAobWF4KWAgPSBwcm9wb3J0aW9uX2JhcigibGlnaHRibHVlIikKICAgICkKICApCmBgYAoKIyMgbmRIIHVuZCBMTUItRGF0ZW4gKG5pY2h0IG9mZmVuKQoKYGBge3J9Cm5kaF9sbWIgPSByZWFkX2V4Y2VsKCdkb3dubG9hZC9MTUJfbmRIMjAxNS54bHN4JykgJT4lIHNlbGVjdCgKICBlbnRpdHlfaWQ9QlNOLAogIG5kSD1gQW50ZWlsIG5kSGAsCiAgTE1CPWBBbnRlaWwgTE1CYAopCm5kaF9sbWIKYGBgCgoKIyMgRGF0ZW4gZsO8ciBkaWUgQXBwIHNwZWljaGVybgoKLSBlbnRpdGllcy5nZW9qc29uCi0gZW50aXRpZXMuY3N2Ci0gdW5pdHMuZ2VvanNvbgotIHVuaXRzLmNzdgotIHdlaWdodHMuY3N2Ci0gYXNzaWdubWVudC5jc3YKCiMjIyBOZXVlIERhdGVuCgoKIyMjIyBFbnRpdGllcyAvIFNjaHVsZW4KCmBgYHtyfQplbnRpdGllcyA9IHN1YnNldChyZV9zY2h1bHN0YW5kX2RmLCBzcGF0aWFsX25hbWUgJWluJSByZWxldmFudF9zY2hvb2xzKSAlPiUKICByZW5hbWUoZW50aXR5X2lkID0gc3BhdGlhbF9uYW1lKSAlPiUKICBzZWxlY3QoLWdtbF9pZCwgLXNwYXRpYWxfYWxpYXMsIC1zcGF0aWFsX3R5cGUpICU+JQogIGlubmVyX2pvaW4ocmVuYW1lKHJlbGV2YW50X2thcGFzLCBjYXBhY2l0eT1LYXBhKSwgYnk9YygnZW50aXR5X2lkJz0nU2NodWxudW1tZXInKSkgJT4lCiAgbGVmdF9qb2luKG5kaF9sbWIsIGJ5PWMoJ2VudGl0eV9pZCcpKQpjb29yZGluYXRlcyhlbnRpdGllcykgPSB+IGxvbiArIGxhdApwcm9qNHN0cmluZyhlbnRpdGllcykgPSBDUlMoIitwcm9qPWxvbmdsYXQgK2VsbHBzPVdHUzg0ICtkYXR1bT1XR1M4NCArbm9fZGVmcyIpCmZpbGUucmVtb3ZlKCdhcHAvZGF0YS9lbnRpdGllcy5nZW9qc29uJykKZW50aXRpZXMgJT4lIHdyaXRlT0dSKCdhcHAvZGF0YS9lbnRpdGllcy5nZW9qc29uJywgbGF5ZXI9ImVudGl0aWVzIiwgZHJpdmVyPSJHZW9KU09OIiwgY2hlY2tfZXhpc3RzPUYpCmVudGl0aWVzQGRhdGEgJT4lIHNlbGVjdChlbnRpdHlfaWQsIGNhcGFjaXR5KSAlPiUgd3JpdGVfY3N2KCdhcHAvZGF0YS9lbnRpdGllcy5jc3YnKQpgYGAKCiMjIyMgVW5pdHMgLyBTdGF0aXN0aXNjaGUgQmzDtmNrZQoKYGBge3J9CnVuaXRzID0gc3Vic2V0KGJsaywgQkVaID09IGJlel9pZCkKCnVuaXRzQGRhdGEkUExSID0gTlVMTAp1bml0c0BkYXRhJEVpbncgPSBOVUxMCnVuaXRzQGRhdGEkQkVaID0gTlVMTAp1bml0c0BkYXRhJHVuaXRfaWQgPSB1bml0c0BkYXRhJEJMSwp1bml0c0BkYXRhJEJMSyA9IE5VTEwKCnVuaXRzID0gdW5pdHMgJT4lIHNwOjptZXJnZSgKICBvcHRpbV9raWRzX2luX2Jsa3MgJT4lCiAgICBsZWZ0X2pvaW4oc2diSUlfYmxrKSAlPiUKICAgIHNlbGVjdCh1bml0X2lkPUJMSywgcG9wdWxhdGlvbj1raWRzLCBzZ2JJSXU2NSkKICApCgpmaWxlLnJlbW92ZSgnYXBwL2RhdGEvdW5pdHMuZ2VvanNvbicpCnVuaXRzICU+JSB3cml0ZU9HUignYXBwL2RhdGEvdW5pdHMuZ2VvanNvbicsIGxheWVyPSJlbnRpdGllcyIsIGRyaXZlcj0iR2VvSlNPTiIsIGNoZWNrX2V4aXN0cz1GKQp1bml0c0BkYXRhICU+JSB3cml0ZV9jc3YoJ2FwcC9kYXRhL3VuaXRzLmNzdicpCmBgYAoKIyMjIyBadW9yZG51bmcKCmBgYHtyfQphc3NpZ25tZW50ID0gc29sdXRpb24gJT4lIHNlbGVjdCh1bml0X2lkPUJMSywgZW50aXR5X2lkPXNjaG9vbCkKYXNzaWdubWVudCAlPiUgd3JpdGVfY3N2KCdhcHAvZGF0YS9hc3NpZ25tZW50LmNzdicpCmBgYAoKIyMjIyBXZWlnaHRzIC8gU2NodWx3ZWdlCgpgYGB7cn0KdHJhdmVsX2Zyb21fYmxrcyAlPiUKICByZW5hbWUodW5pdF9pZD1CTEssIGVudGl0eV9pZD1kc3QpICU+JQogICNnYXRoZXIod2VpZ2h0LCB2YWx1ZSwgLXVuaXRfaWQsIC1lbnRpdHlfaWQpICU+JQogIHdyaXRlX2NzdignYXBwL2RhdGEvd2VpZ2h0cy5jc3YnKQpgYGAKCiMjIyMgWnVzw6R0emxpY2hlIERhdGVuCgpgYGB7cn0KZmlsZS5jb3B5KCdkb3dubG9hZC9SQlNfT0RfQkVaXzIwMTVfMTIuZ2VvanNvbicsICdhcHAvZGF0YS9SQlNfT0RfQkVaXzIwMTVfMTIuZ2VvanNvbicpCmBgYAoK